unit main;
(*  cssMonkey
    Copyright (C) 2011 Matthew Hipkin <http://www.matthewhipkin.co.uk>

    This Source Code Form is subject to the terms of the Mozilla Public
    License, v. 2.0. If a copy of the MPL was not distributed with this
    file, You can obtain one at http://mozilla.org/MPL/2.0/. *)
{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil, SynEdit, SynHighlighterCss, SynHighlighterHTML,
  Forms, Controls, Graphics, Dialogs, ComCtrls, StdCtrls, ExtCtrls, Menus,
  Buttons, LCLIntf, StrUtils, IniFiles, XiPanel, cssparser, miscfunc;

type

  { TfrmMain }

  TfrmMain = class(TForm)
    dialogFind: TFindDialog;
    labelUpdate: TLabel;
    listOpenFiles: TListBox;
    menuHelp: TMenuItem;
    menuCopy: TMenuItem;
    menuFind: TMenuItem;
    menuFileList: TMenuItem;
    menuAddQL: TMenuItem;
    menuQLManage: TMenuItem;
    menuViewQuickLinks: TMenuItem;
    menuView: TMenuItem;
    menuOptions: TMenuItem;
    menuReplace: TMenuItem;
    menuPaste: TMenuItem;
    menuCut: TMenuItem;
    menuWindow: TMenuItem;
    menuOnlineHelp: TMenuItem;
    menuAbout: TMenuItem;
    menuEdit: TMenuItem;
    menuQuickLinks: TMenuItem;
    menuMain: TMainMenu;
    menuFile: TMenuItem;
    menuExit: TMenuItem;
    menuCloseCurrentFile: TMenuItem;
    menuNewElement: TMenuItem;
    menuEditElement: TMenuItem;
    menuDeleteElement: TMenuItem;
    menuNewFile: TMenuItem;
    menuNewFile2: TMenuItem;
    menuSave: TMenuItem;
    menuSaveAs: TMenuItem;
    menuOpenFile: TMenuItem;
    menuOpenURL: TMenuItem;
    dialogOpen: TOpenDialog;
    pagesMain: TPageControl;
    btnNewFile: TSpeedButton;
    btnOpenFile: TSpeedButton;
    btnOpenURL: TSpeedButton;
    btnSave: TSpeedButton;
    btnSaveAs: TSpeedButton;
    btnFind: TSpeedButton;
    btnSaveAll: TSpeedButton;
    btnCloseFile: TSpeedButton;
    btnOptions: TSpeedButton;
    btnCut: TSpeedButton;
    btnCopy: TSpeedButton;
    btnPaste: TSpeedButton;
    menuPopupFiles: TPopupMenu;
    btnAddElement: TSpeedButton;
    menuElements: TPopupMenu;
    dialogSave: TSaveDialog;
    btnDeleteElement: TSpeedButton;
    panelQuickLinks: TPanel;
    statusMain: TStatusBar;
    cssHighlighter: TSynCssSyn;
    updatesTimer: TTimer;
    toolbarMain: TToolBar;
    ToolButton1: TToolButton;
    ToolButton2: TToolButton;
    ToolButton3: TToolButton;
    procedure btnCloseFileClick(Sender: TObject);
    procedure btnFindClick(Sender: TObject);
    procedure btnSaveAllClick(Sender: TObject);
    procedure dialogFindFind(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: boolean);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure labelUpdateClick(Sender: TObject);
    procedure listOpenFilesClick(Sender: TObject);
    procedure menuAboutClick(Sender: TObject);
    procedure menuElementsPopup(Sender: TObject);
    procedure menuExitClick(Sender: TObject);
    procedure menuNewElementClick(Sender: TObject);
    procedure menuAddQLClick(Sender: TObject);
    procedure menuDeleteElementClick(Sender: TObject);
    procedure menuEditElementClick(Sender: TObject);
    procedure menuFileListClick(Sender: TObject);
    procedure menuNewFileClick(Sender: TObject);
    procedure menuOnlineHelpClick(Sender: TObject);
    procedure menuOptionsClick(Sender: TObject);
    procedure menuPopupFilesPopup(Sender: TObject);
    procedure menuOpenFileClick(Sender: TObject);
    procedure menuOpenURLClick(Sender: TObject);
    procedure menuQLManageClick(Sender: TObject);
    procedure menuViewQuickLinksClick(Sender: TObject);
    procedure menuSaveAsClick(Sender: TObject);
    procedure menuSaveClick(Sender: TObject);
    procedure updatesTimerTimer(Sender: TObject);
  private
    { private declarations }
    tabs: TList;
    editorFont: TFont;
    editorBG: TColor;
    updatePanel: TXiPanel;
    procedure parseCSS(const css: String; fname: String; addNew: Boolean);
    function buildCSS: String;
    procedure downloadDefinitions;
    function findActiveTab: Integer;
    procedure windowMenuClick(Sender: TObject);
    procedure quickLinkClick(Sender: TObject);
    procedure treeItemClick(Sender: TObject);
    procedure SynEditChange(Sender: TObject);
    procedure loadConfig;
    procedure saveConfig;
    procedure addQuickLink(key: String; value: String; showButton: Boolean);
  public
    { public declarations }
    procedure loadQuickLinks;
    procedure saveQuickLinks;
  end;

var
  frmMain: TfrmMain;

implementation

{$R *.lfm}

uses urlentry, elements, about, config, quicklinks, closeconf, help;

{ TfrmMain }

{ CUSTOM FUNCTIONS }

procedure TfrmMain.loadQuickLinks;
var
  x: integer;
  lines: TStrings;
  parts: TArray;
begin
  { Quick Link file format:
       tagname|element1:value;element2:value|showontoolbar0/1 }
  if FileExists(appdir + 'quicklinks.lst') then
  begin
    lines := TStringList.Create;
    lines.LoadFromFile(appdir + 'quicklinks.lst');
    for x := 0 to lines.Count -1 do
    begin
      parts := explode('|',lines[x],0);
      if High(parts) > 0 then
      begin
        if parts[2] = '0' then addQuickLink(parts[0],parts[1],false)
        else addQuickLink(parts[0],parts[1],true);
      end;
      parts := nil;
    end;
    lines.Free;
  end;
end;

procedure TfrmMain.saveQuickLinks;
var
  f: ^TFavItem;
  x, y: integer;
  lines: TStrings;
  tmp: String;
begin
  lines := TStringList.Create;
  for x := 0 to qLinks.Count -1 do
  begin
    f := qLinks[x];
    tmp := f^.key + '|';
    for y := 0 to High(f^.values) do tmp := tmp + f^.values[y] + ';';
    if f^.showButton = true then tmp := tmp + '|1'
    else tmp := tmp + '|0';
    lines.Add(tmp);
  end;
  lines.SaveToFile(appdir + 'quicklinks.lst');
  lines.Free;
end;

procedure TfrmMain.addQuickLink(key: String; value: String; showButton: Boolean);
var
  f: ^TFavItem;
  y: integer;
begin
  new(f);
  f^.key := key;
  f^.values := explode(';',value,0);
  if showButton = true then
  begin
    f^.button := TButton.Create(panelQuickLinks);
    f^.button.Parent := panelQuickLinks;
    f^.button.Height := 25;
    f^.button.Caption := f^.key;
    f^.button.Hint := '';
    for y := 0 to High(f^.values) do
      f^.button.Hint := f^.button.Hint + f^.values[y] + Chr(10);
    f^.button.ShowHint := true;
    f^.button.Align := alTop;
    f^.button.OnClick := @quickLinkClick;
    f^.showButton := true;
  end else f^.showButton := false;
  f^.menu := TMenuItem.Create(menuQuickLinks);
  f^.menu.Caption := f^.key;
  f^.menu.OnClick := @quickLinkClick;
  menuQuickLinks.Insert(0,f^.menu);
  qLinks.Add(f);
end;

procedure TfrmMain.downloadDefinitions;
var
  response: TStrings;
begin
  if (not FileExists(appdir+'taglist.txt')) and (not FileExists(appdir+'elementlist.txt')) then
  begin
    if MessageDlg('Unable to find HTML and CSS definition lists, would you like to download them now?'#13#10'They are only small and will take a second to download.',mtInformation, mbOKCancel, 0) = mrOK then
    begin
      response := TStringList.Create;
      response.Text := httpGet('http://www.matthewhipkin.co.uk/res/taglist.txt');
      response.SaveToFile(appdir+'taglist.txt');
      response.Clear;
      response.Text := httpGet('http://www.matthewhipkin.co.uk/res/elementlist.txt');
      response.SaveToFile(appdir+'elementlist.txt');
      response.Free;
      showmessage('Download complete!');
    end;
  end;
end;

function TfrmMain.findActiveTab: Integer;
var
  tab: TTabSheet;
  i: Integer;
  t: ^TTabItem;
begin
  Result := -1;
  tab := pagesMain.ActivePage;
  for i := 0 to tabs.Count -1 do
  begin
    t := tabs[i];
    if tab = t^.tab then
    begin
      Result := i;
      break;
    end;
  end;
end;

procedure TfrmMain.parseCSS(const css: String; fname: String; addNew: Boolean);
var
  ParentNode: TTreeNode;
  Parser: TCSSParser;
  t: ^TTabItem;
  cssOrig: TStrings;
  tpos: TPoint;
begin
  Parser := TCSSParser.Create(css);
  cssOrig := TStringList.Create;
  cssOrig.Text := css;
  if addNew then
  begin
    new(t);
    t^.tab := TTabSheet.Create(pagesMain);
    t^.tab.Parent := pagesMain;
    t^.tab.Caption := ExtractFilename(fname);
    pagesMain.ActivePage := t^.tab;
    t^.s := TSplitter.Create(t^.tab);
    t^.s.Parent := t^.tab;
    t^.s.Align := alLeft;
    t^.s.Width := 5;
    t^.tree := TTreeView.Create(t^.tab);
    t^.tree.Parent := t^.tab;
    t^.tree.align := alLeft;
    t^.tree.Width := 198;
    t^.tree.ReadOnly := true;
    t^.tree.OnClick := @treeItemClick;
    t^.tree.PopupMenu := menuElements;
    t^.edit := TSynEdit.Create(t^.tab);
    t^.edit.Parent := t^.tab;
    t^.edit.Align := alClient;
    t^.edit.Highlighter := cssHighlighter;
    t^.edit.Font := editorFont;
    t^.edit.Color := editorBG;
    t^.edit.Font.Quality := fqDefault;
    t^.edit.Gutter.Visible := false;
    t^.edit.OnChange := @SynEditChange;
    if FileExists(t^.localFilename) then
    begin
      t^.localFilename := fname;
      t^.isSaved := true;
    end
    else
    begin
      t^.URL:=fname;
      t^.isSaved := false;
    end;
    tabs.Add(t);
    t^.menu := TMenuItem.Create(menuWindow);
    t^.menu.Caption:= fname;
    t^.menu.OnClick:= @windowMenuClick;
    menuWindow.Add(t^.menu);
    listOpenFiles.ItemIndex:= listOpenFiles.Items.Add(ExtractFileName(fname));
    listOpenFilesClick(listOpenFiles);
  end
  else
  begin
    t := tabs[findActiveTab];
    t^.tree.Items.Clear;
    t^.isSaved := false;
  end;
  tpos := t^.edit.CaretXY;
  ParentNode := TTreeNode.Create(t^.tree.Items);
  with t^.tree.Items do
  try
    BeginUpdate;
    while Parser.Next do
    begin
      case Parser.ItemType of
        itHead: ParentNode := InsertBehind(ParentNode,Parser.Item);
        itLast: begin if Parser.Item <> '' then AddChild(ParentNode, Parser.Item); end;
        //itSimple: AddChild(ParentNode, Parser.Item);
        //itComment: begin if ParentNode.HasAsParent(nil) then InsertBehind(ParentNode,Parser.Item) else AddChild(ParentNode,Parser.Item); end;
        else begin if Parser.Item <> '' then AddChild(ParentNode, Parser.Item); end;
      end;
    end;
  finally
    ParentNode.Expand(true);
    EndUpdate;
  end;
  t^.edit.Lines.Clear;
  t^.edit.Lines.AddStrings(cssOrig);
  t^.edit.CaretXY := tpos;
  Parser.Free;
  cssOrig.Free;
end;

{ This function needs to either go or be refactored as a code tidy function }
function TfrmMain.buildCSS: String;
var
  newStr: TStrings;
  Node: TTreeNode;
  Node2: TTreeNode;
  t: ^TTabItem;
begin
  t := tabs[findActiveTab];
  // Re-populate the synedit when an item is edited in the treeview
  newStr := TStringList.Create;
  Node := TTreeNode.Create(t^.tree.Items);
  Node2 := TTreeNode.Create(t^.tree.Items);
  Node := t^.tree.Items[0];
  while Node <> nil do
  begin
    if Node.HasChildren then
    begin
      newStr.Add(Node.Text + ' {');
      Node2 := Node.Items[0];
      while Node2 <> nil do
      begin
        if Trim(AnsiRightStr(Node2.Text,2)) <> '*/' then
          newStr.Add('  '+Node2.Text+';')
        else
        newStr.Add('  '+Node2.Text);
        Node2 := Node2.GetNextChild(Node2);
      end;
      newStr.Add('}');
      newStr.Add('');
    end
    else newStr.Add(Node.Text);
    Node := Node.GetNextSkipChildren;
  end;
  Result := newStr.Text;
  newStr.Free;
  Node.Free;
  Node2.Free;
end;

procedure TfrmMain.windowMenuClick(Sender: TObject);
var
  x: integer;
  n: String;
  t: ^TTabItem;
begin
  n := (Sender as TMenuItem).Caption;
  for x := 0 to tabs.Count -1 do
  begin
    t := tabs[x];
    if (t^.localFilename = n) or (t^.URL = n) then
    begin
      listOpenFiles.ItemIndex := x;
      listOpenFilesClick(Sender);
      break;
    end;
  end;
end;

procedure TfrmMain.quickLinkClick(Sender: TObject);
var
  t: ^TTabItem;
  f: ^TFavItem;
  key: String;
  i: integer;
  tmp: String;
  l: TListItem;
  n: TTreeNode;
begin
  if findActiveTab < 0 then exit;
  t := tabs[findActiveTab];
  if Sender is TButton then key := (Sender as TButton).Caption;
  if Sender is TMenuItem then key := (Sender as TMenuItem).Caption;
  for i := 0 to qLinks.Count -1 do
  begin
    f := qLinks[i];
    if f^.key = key then break;
  end;
  downloadDefinitions;
  frmElement.Caption := 'Editing '+f^.key;
  frmElement.tags.LoadFromFile(appdir+'taglist.txt');
  frmElement.elements.LoadFromFile(appdir+'elementlist.txt');
  frmElement.textAttributes.Text := '';
  frmElement.textElementName.Text := f^.key;
  frmElement.textValues.Text := '';
  frmElement.checkShow.Visible := false;
  frmElement.listAttributes.Items.Clear;
  for i := 0 to High(f^.values) do
  begin
    l := frmElement.listAttributes.Items.Add;
    tmp := f^.values[i];
    l.Caption:=Copy(tmp,1,Pos(':',tmp)-1);
    l.SubItems.Add(Copy(tmp,Pos(':',tmp)+1,Length(tmp)));
  end;
  if frmElement.ShowModal = mrOK then
  begin
    n := TTreeNode.Create(t^.tree.Items);
    with t^.tree.Items do
    begin
      n := InsertBehind(n,frmElement.textElementName.Text);
      for i := 0 to frmElement.listAttributes.Items.Count-1 do
      begin
        AddChild(n, frmElement.listAttributes.Items[i].Caption + ': ' + frmElement.listAttributes.Items[i].SubItems[0]);
      end;
    end;
    t^.isSaved := false;
    t^.edit.Lines.Text := buildCSS;
  end;
end;

procedure TfrmMain.treeItemClick(Sender: TObject);
var
  t: ^TTabItem;
  x: integer;
  p: integer;
begin
  t := tabs[findActiveTab];
  if t^.tree.SelectionCount < 1 then exit;
  if t^.tree.Selected.Parent = nil then
  begin
    // Normal element
    if t^.tree.Selected.HasChildren then
    begin
      p := Pos(t^.tree.Selected.Text + ' {',t^.edit.Lines.Text);
      if p < 1 then
        p := Pos(t^.tree.Selected.Text + '{',t^.edit.Lines.Text);
      t^.edit.SelStart := p;
      t^.edit.SelEnd := p + Length(t^.tree.Selected.Text);
    end
    // Comment
    else if (not t^.tree.Selected.HasChildren) and (Pos('/*',t^.tree.Selected.Text) > 0) then
    begin
      p := Pos(t^.tree.Selected.Text,t^.edit.Lines.Text);
      if p < 1 then
        p := Pos(t^.tree.Selected.Text,t^.edit.Lines.Text);
      t^.edit.SelStart := p;
      t^.edit.SelEnd := p + Length(t^.tree.Selected.Text);
    end;
  end
  else
  begin
      // Child element
  end;
end;

{ FORM EVENTS }

procedure TfrmMain.FormCreate(Sender: TObject);
begin
  tabs := TList.Create;
  // Set the main form title
  frmMain.Caption := 'cssMonkey '+APPVER;
  // Setup a working directory for config files
  appdir :=  GetUserDir + '.cssmonkey' + PathDelim;
  if not DirectoryExists(appdir) then mkdir(appdir);
  dialogOpen.Filter := cssHighlighter.DefaultFilter;
  dialogSave.Filter := cssHighlighter.DefaultFilter;
  editorFont := TFont.Create;
  qLinks := TList.Create;
  loadConfig;
  loadQuicklinks;
  updatesTimer.Enabled := true;
end;

procedure TfrmMain.btnCloseFileClick(Sender: TObject);
var
  t: ^TTabItem;
  x: Integer;
begin
  if listOpenFiles.ItemIndex > -1 then
  begin
    // Free up the controls and delete from the lists
    t := tabs[listOpenFiles.ItemIndex];
    t^.edit.Free;
    t^.s.Free;
    t^.tree.Free;
    t^.tab.Free;
    t^.menu.Free;
    tabs.Delete(listOpenFiles.ItemIndex);
    dispose(t);
    x := listOpenFiles.ItemIndex -1;
    listOpenFiles.Items.Delete(listOpenFiles.ItemIndex);
    { Set the selected file to the previous item and call listOpenFilesClick
      to proc a UI update }
    listOpenFiles.ItemIndex := x;
    listOpenFilesClick(Sender);
  end;
end;

procedure TfrmMain.btnFindClick(Sender: TObject);
begin
  if findActiveTab < -1 then exit;
  dialogFind.Execute;
end;

procedure TfrmMain.btnSaveAllClick(Sender: TObject);
begin
//
end;

procedure TfrmMain.dialogFindFind(Sender: TObject);
var
  t: ^TTabItem;
  Node: TTreeNode;
  found: Boolean;
  fText,sText: String;
begin
  t := tabs[findActiveTab];
  Node := TTreeNode.Create(t^.tree.Items);
  found := false;
  if frDown in dialogFind.Options then
  begin
    if t^.tree.Selected = nil then Node := t^.tree.Items.GetFirstNode
    else Node := t^.tree.Selected.GetNext;
  end
  else
  begin
    if t^.tree.Selected = nil then Node := t^.tree.Items.GetLastNode
    else Node := t^.tree.Selected.GetPrev;
  end;
  while Node <> nil do
  begin
    if frMatchCase in dialogFind.Options then
    begin
      fText := dialogFind.FindText;
      sText := Node.Text;
    end
    else
    begin
      fText := Lowercase(dialogFind.FindText);
      sText := Lowercase(Node.Text);
    end;
    if Pos(fText,sText) > 0 then
    begin
      t^.tree.Selected := Node;
      found := true;
      break;
    end;
    if frDown in dialogFind.Options then Node := Node.GetNext
    else Node := Node.GetPrev;
  end;
  if not found then
  begin
    dialogFind.CloseDialog;
    showmessage('Finished searching document');
  end;
end;

procedure TfrmMain.FormCloseQuery(Sender: TObject; var CanClose: boolean);
var
  x: integer;
  t: ^TTabItem;
  unsaved: TStrings;
begin
  unsaved := TStringList.Create;
  for x := 0 to tabs.Count -1 do
  begin
    t := tabs[x];
    if t^.isSaved = false then
    begin
      CanClose := false;
      if t^.localFilename <> '' then unsaved.Add(t^.localFilename)
      else unsaved.Add(t^.URL);
    end;
  end;
  if CanClose = false then
  begin
    with frmCloseConfirm do
    begin
      listFiles.Clear;
      listFiles.Items.AddStrings(unsaved);
      if ShowModal = mrOK then
      begin
        if saveAll = true then btnSaveAllClick(Sender);
        CanClose := true;
      end
      else CanClose := false;
    end;
  end;
  unsaved.Free;
end;

procedure TfrmMain.FormDestroy(Sender: TObject);
begin
  tabs.Free;
  qLinks.Free;
end;

procedure TfrmMain.labelUpdateClick(Sender: TObject);
begin
  OpenURL('http://www.matthewhipkin.co.uk');
end;

procedure TfrmMain.listOpenFilesClick(Sender: TObject);
var
  t: ^TTabItem;
begin
  if listOpenFiles.ItemIndex < 0 then exit;
  // Set the active page and status panels to the selected file
  t := tabs[listOpenFiles.ItemIndex];
  pagesMain.ActivePage := t^.tab;
  frmMain.Caption:='cssMonkey ' + APPVER + ' [' + listOpenFiles.Items[listOpenFiles.ItemIndex] + ']';
  if t^.localFilename <> '' then statusMain.Panels[0].Text := t^.localFilename
  else statusMain.Panels[0].Text := t^.URL;
  if t^.isSaved then statusMain.Panels[1].Text := ''
  else statusMain.Panels[1].Text := 'Modified';
end;

procedure TfrmMain.menuAboutClick(Sender: TObject);
begin
  frmAbout.Label1.Caption := 'cssMonkey '+APPVER;
  frmAbout.ShowModal;
end;

procedure TfrmMain.menuElementsPopup(Sender: TObject);
var
  t: ^TTabItem;
begin
  t := tabs[findActiveTab];
  if t^.tree.Selected = nil then
  begin
    menuDeleteElement.Enabled := false;
    menuEditElement.Enabled := false;
    menuAddQL.Enabled := false;
  end
  else
  begin
    menuDeleteElement.Enabled := true;
    menuEditElement.Enabled := true;
    menuAddQL.Enabled := true;
  end;
end;

procedure TfrmMain.menuExitClick(Sender: TObject);
begin
  // Loop through all open tabs, and checked isSaved
end;

procedure TfrmMain.menuAddQLClick(Sender: TObject);
var
  t: ^TTabItem;
  n: TTreeNode;
  v: String;
begin
  t := tabs[findActiveTab];
  if t^.tree.Selected = nil then exit;
  if t^.tree.Selected.Parent <> nil then t^.tree.Selected := t^.tree.Selected.Parent;
  n := t^.tree.Selected.GetFirstChild;
  v := '';
  while n <> nil do
  begin
    v := v + n.Text + ';';
    n := t^.tree.Selected.GetNextChild(n);
  end;
  addQuickLink(t^.tree.Selected.Text, v, true);
  saveQuickLinks;
end;

procedure TfrmMain.menuNewElementClick(Sender: TObject);
var
  t: ^TTabItem;
  n: TTreeNode;
  newCSS: TStrings;
  i: integer;
begin
  if findActiveTab < 0 then exit;
  t := tabs[findActiveTab];
  downloadDefinitions;
  frmElement.Caption:='New Element';
  frmElement.tags.LoadFromFile(appdir+'taglist.txt');
  frmElement.elements.LoadFromFile(appdir+'elementlist.txt');
  frmElement.textAttributes.Text := '';
  frmElement.textElementName.Text := '';
  frmElement.textValues.Text := '';
  frmElement.listAttributes.Items.Clear;
  frmElement.checkShow.Visible := false;
  if frmElement.ShowModal = mrOK then
  begin
    newCSS := TStringList.Create;
    n := TTreeNode.Create(t^.tree.Items);
    with t^.tree.Items do
    begin
      n := InsertBehind(n,frmElement.textElementName.Text);
      newCSS.Add(frmElement.textElementName.Text + ' {');
      for i := 0 to frmElement.listAttributes.Items.Count-1 do
      begin
        AddChild(n, frmElement.listAttributes.Items[i].Caption + ': ' + frmElement.listAttributes.Items[i].SubItems[0]);
        newCSS.Add('  '+frmElement.listAttributes.Items[i].Caption + ': ' + frmElement.listAttributes.Items[i].SubItems[0] + ';');
      end;
      n.Expand(true);
      newCSS.Add('}');
    end;
    t^.isSaved := false;
//    t^.edit.Lines.Text := buildCSS;
    t^.edit.Lines.AddStrings(newCSS);
    newCSS.Free;
  end;
end;

procedure TfrmMain.menuDeleteElementClick(Sender: TObject);
var
  t: ^TTabItem;
begin
  if findActiveTab < 0 then exit;
  t := tabs[findActiveTab];
  if t^.tree.Selected = nil then exit;
  t^.tree.Selected.Delete;
  t^.edit.Lines.Text := buildCSS;
end;

procedure TfrmMain.menuEditElementClick(Sender: TObject);
var
  t: ^TTabItem;
  l: TListItem;
  tmp: String;
  editStr: String;
  i: integer;
begin
  { This needs to be recoded so as not to use the buildCSS function. Using
    the same method as in treeitemClick() and then walking the SynEdit text as
    a string should be the best way to find the current block of CSS code }
  if findActiveTab < 0 then exit;
  t := tabs[findActiveTab];
  if t^.tree.Selected = nil then exit;
  if t^.tree.Selected.Parent <> nil then t^.tree.Selected := t^.tree.Selected.Parent;
  downloadDefinitions;
//  editStr := stripEverything('');
  frmElement.Caption := 'Editing '+t^.tree.Selected.Text;
  frmElement.tags.LoadFromFile(appdir+'taglist.txt');
  frmElement.elements.LoadFromFile(appdir+'elementlist.txt');
  frmElement.textAttributes.Text := '';
  frmElement.textElementName.Text := t^.tree.Selected.Text;
  frmElement.textValues.Text := '';
  frmElement.listAttributes.Items.Clear;
  frmElement.checkShow.Visible := false;
  for i := 0 to t^.tree.Selected.Count -1 do
  begin
    l := frmElement.listAttributes.Items.Add;
    tmp := t^.tree.Selected.Items[i].Text;
    l.Caption:=Copy(tmp,1,Pos(':',tmp)-1);
    l.SubItems.Add(Copy(tmp,Pos(':',tmp)+1,Length(tmp)));
  end;
  if frmElement.ShowModal = mrOK then
  begin
    t^.tree.Selected.DeleteChildren;
    t^.tree.Selected.Text := frmElement.textElementName.Text;
    for i := 0 to frmElement.listAttributes.Items.Count-1 do
    begin
      t^.tree.Items.AddChild(t^.tree.Selected, frmElement.listAttributes.Items[i].Caption + ': ' + frmElement.listAttributes.Items[i].SubItems[0]);
    end;
    t^.isSaved := false;
    t^.edit.Lines.Text := buildCSS;
  end;
end;

procedure TfrmMain.menuFileListClick(Sender: TObject);
begin
  listOpenFiles.Visible := not listOpenFiles.Visible;
  menuFileList.Checked:= listOpenFiles.Visible;
end;

procedure TfrmMain.menuNewFileClick(Sender: TObject);
var
  x,y,i: integer;
  t, t2: String;
begin
  i := 0;
  y := 1;
  // Nasty way to auto-number untitled documents, but it works :D
  for x := 0 to pagesMain.PageCount-1 do
  begin
    t := pagesMain.Pages[x].Caption;
    t2 := '';
    // Extract the numbers from the string
    for i := 1 to Length(t) do
    begin
      // We're looking for untitled and a number
      if (t[i] in ['0'..'9']) and (Copy(t,1,8) = 'untitled') then t2 := t2 + t[i];
    end;
    // If we've found a number convert to an integer
    if t2 <> '' then i := StrToInt(t2)
    // Otherwise reset the counter
    else i := 0;
    // Increment our untitled number
    y := i + 1;
  end;
  // Call parseCSS with no text and the new untitled filename
  parseCSS('','untitled'+IntToStr(y)+'.css',true);
end;

procedure TfrmMain.menuOnlineHelpClick(Sender: TObject);
begin
  //OpenURL('http://www.cssmonkey.matthewhipkin.co.uk/docs.php');
  frmHelp.Show;
end;

procedure TfrmMain.menuOptionsClick(Sender: TObject);
var
  x: integer;
  t: ^TTabItem;
begin
  frmOptions.btnBgColour.ButtonColor := editorBG;
  frmOptions.textFont.Text := editorFont.Name;
  frmOptions.textFontSize.Value := editorFont.Size;
  frmOptions.editPreview.Font := editorFont;
  frmOptions.highlighterCSS.CommentAttri := cssHighlighter.CommentAttri;
  frmOptions.highlighterCSS.IdentifierAttri := cssHighlighter.IdentifierAttri;
  frmOptions.highlighterCSS.KeyAttri := cssHighlighter.KeyAttri;
  frmOptions.highlighterCSS.NumberAttri := cssHighlighter.NumberAttri;
  frmOptions.highlighterCSS.StringAttri := cssHighlighter.StringAttri;
  frmOptions.highlighterCSS.SymbolAttri := cssHighlighter.SymbolAttri;
  if frmOptions.ShowModal = mrOK then
  begin
    saveConfig;
    for x := 0 to tabs.Count -1 do
    begin
      t := tabs[x];
      if t^.edit <> nil then
      begin
        t^.edit.Color := frmOptions.btnBgColour.ButtonColor;
        t^.edit.Font := frmOptions.dialogFont.Font;
      end;
    end;
    loadConfig;
  end;
end;

procedure TfrmMain.menuPopupFilesPopup(Sender: TObject);
begin
  if listOpenFiles.ItemIndex < 0 then
  begin
    menuCloseCurrentFile.Enabled := false;
  end
  else
  begin
    menuCloseCurrentFile.Enabled := true;
  end;
end;

procedure TfrmMain.menuOpenFileClick(Sender: TObject);
var
  c: TStrings;
  x: integer;
  t: ^TTabItem;
  loadFile: Boolean;
begin
  loadFile := true;
  if dialogOpen.Execute then
  begin
    for x := 0 to tabs.Count -1 do
    begin
      t := tabs[x];
      if t^.localFilename = dialogOpen.FileName then
      begin
        loadFile := false;
        break;
      end;
    end;
    if loadFile then
    begin
      c := TStringList.Create;
      c.LoadfromFile(dialogOpen.Filename);
      parseCSS(c.Text,dialogOpen.Filename,true);
      c.Free;
    end;
  end;
end;

procedure TfrmMain.menuOpenURLClick(Sender: TObject);
var
  response: TStrings;
  x: integer;
  addNewLink: Boolean;
  t: ^TTabItem;
  loadFile: Boolean;
begin
  loadFile := true;
  if FileExists(appdir + 'urls.lst') then
    frmURL.textURL.Items.LoadFromFile(appdir + 'urls.lst');
  frmURL.textURL.Text := 'http://';
  if frmURL.ShowModal = mrOK then
  begin
    for x := 0 to tabs.Count -1 do
    begin
      t := tabs[x];
      if t^.URL = frmURL.textURL.Text then
      begin
        loadFile := false;
        break;
      end;
    end;
    if loadFile then
    begin
      response := TStringList.Create;
      response.Text := httpGet(frmURL.textURL.Text);
      parseCSS(response.Text,frmURL.textURL.Text,true);
      response.Free;
      addNewLink := true;
      for x := 0 to frmURL.textURL.Items.Count -1 do
      begin
        if frmURL.textURL.Items[x] = frmURL.textURL.Text then addNewLink := false;
      end;
      if addNewLink = true then frmURL.textURL.Items.Add(frmURL.textURL.Text);
      frmURL.textURL.Items.SaveToFile(appdir + 'urls.lst');
    end;
  end;
end;

procedure TfrmMain.menuQLManageClick(Sender: TObject);
var
  x: integer;
  f: ^TFavItem;
begin
  frmManageQL.listItems.Clear;
  for x := 0 to qLinks.Count -1 do
  begin
    f := qLinks[x];
    frmManageQL.listItems.Items.Add(f^.key);
  end;
  if frmManageQL.ShowModal = mrOK then
  begin
    SaveQuicklinks;
    for x := qLinks.Count -1 downto 0 do
    begin
      f := qLinks[x];
      if f^.button <> nil then f^.button.Free;
      f^.menu.Free;
      qLinks.Delete(x);
      dispose(f);
    end;
    LoadQuickLinks;
  end;
end;

procedure TfrmMain.menuViewQuickLinksClick(Sender: TObject);
begin
  panelQuickLinks.Visible := not panelQuickLinks.Visible;
  menuViewQuickLinks.Checked := panelQuickLinks.Visible;
end;

procedure TfrmMain.menuSaveAsClick(Sender: TObject);
var
  t: ^TTabItem;
begin
  if findActiveTab < 0 then exit;
  t := tabs[findActiveTab];
  if dialogSave.Execute then
  begin
    t^.localFilename := dialogSave.FileName;
    if t^.localFilename <> '' then t^.edit.Lines.SaveToFile(t^.localFilename);
    t^.isSaved := true;
  end;
end;

procedure TfrmMain.menuSaveClick(Sender: TObject);
var
  t: ^TTabItem;
begin
  t := tabs[findActiveTab];
  if t^.localFilename = '' then
  begin
    if dialogSave.Execute then t^.localFilename := dialogSave.FileName;
  end;
  if t^.localFilename <> '' then
  begin
    t^.edit.Lines.SaveToFile(t^.localFilename);
    t^.isSaved := true;
  end;
end;

procedure TfrmMain.updatesTimerTimer(Sender: TObject);
var
  response: String;
  newVer: Boolean;
begin
  updatesTimer.Enabled := false;
  // Check for a new version comparing the CURRVER variable to the value returned
  newVer := false;
  try
    response := httpGet('http://www.matthewhipkin.co.uk/cssmonkey.txt');
    response := trim(response);
    if CURRVER < StrToInt(response) then newVer := true;
  except
    newVer := false;
  end;
  if newVer then
  begin
    updatePanel := TXiPanel.Create(self);
    updatePanel.Parent := toolbarMain;
    updatePanel.Width := 170;
    updatePanel.Align := alRight;
    updatePanel.ColorScheme := XiPanel.csRose;
    labelUpdate.Parent := updatePanel;
    labelUpdate.Align := alClient;
    labelUpdate.Alignment := taCenter;
    labelUpdate.Layout := tlCenter;
    labelUpdate.Caption := 'A new version is available';
    labelUpdate.Visible := true;
    updatePanel.Visible := false;
    updatePanel.Visible := true;
  end;
end;

procedure TfrmMain.SynEditChange(Sender: TObject);
begin
  parseCSS((Sender as TSynEdit).Lines.Text,'',false);
end;

// Fuglies at the bottom

procedure TfrmMain.loadConfig;
var
  conf: TIniFile;
  t: String;
begin
  if not FileExists(appdir + 'cssmonkey.conf') then
  begin
    editorBG := clNone;
    editorFont.Name := 'Courier New';
    editorFont.Size := 10;
    exit;
  end;
  conf := TIniFile.Create(appdir + 'cssmonkey.conf');
  editorFont.Name := conf.ReadString('highlighter','Font','Courier New');
  editorFont.Size := conf.ReadInteger('highlighter','FontSize',10);
  t := conf.ReadString('highlighter','Colour','');
  if t = '' then editorBG := clNone
  else editorBG := HexToTColor(t);
  // Comments
  t := conf.ReadString('highlighterComments','Colour','');
  if t <> '' then cssHighlighter.CommentAttri.Foreground:=HexToTColor(t);
  if conf.ReadBool('highlighterComments','Bold',false) then
    cssHighlighter.CommentAttri.Style := cssHighlighter.CommentAttri.Style + [fsBold];
  if conf.ReadBool('highlighterComments','Italic',true) then
    cssHighlighter.CommentAttri.Style := cssHighlighter.CommentAttri.Style + [fsItalic];
  if conf.ReadBool('highlighterComments','Underline',false) then
    cssHighlighter.CommentAttri.Style := cssHighlighter.CommentAttri.Style + [fsUnderline];
  // Identifiers
  t  := conf.ReadString('highlighterIdentifiers','Colour','');
  if t <> '' then cssHighlighter.IdentifierAttri.Foreground:=HexToTColor(t);
  if conf.ReadBool('highlighterIdentifiers','Bold',false) then
    cssHighlighter.IdentifierAttri.Style := cssHighlighter.IdentifierAttri.Style + [fsBold];
  if conf.ReadBool('highlighterIdentifiers','Italic',false) then
    cssHighlighter.IdentifierAttri.Style := cssHighlighter.IdentifierAttri.Style + [fsItalic];
  if conf.ReadBool('highlighterIdentifiers','Underline',false) then
    cssHighlighter.IdentifierAttri.Style := cssHighlighter.IdentifierAttri.Style + [fsUnderline];
  // Keys
  t  := conf.ReadString('highlighterKeys','Colour','');
  if t <> '' then cssHighlighter.KeyAttri.Foreground:=HexToTColor(t);
  if conf.ReadBool('highlighterKeys','Bold',true) then
    cssHighlighter.KeyAttri.Style := cssHighlighter.KeyAttri.Style + [fsBold];
  if conf.ReadBool('highlighterKeys','Italic',false) then
    cssHighlighter.KeyAttri.Style := cssHighlighter.KeyAttri.Style + [fsItalic];
  if conf.ReadBool('highlighterKeys','Underline',false) then
    cssHighlighter.KeyAttri.Style := cssHighlighter.KeyAttri.Style + [fsUnderline];
  // Numbers
  t  := conf.ReadString('highlighterNumbers','Colour','');
  if t <> '' then cssHighlighter.NumberAttri.Foreground:=HexToTColor(t);
  if conf.ReadBool('highlighterNumbers','Bold',true) then
    cssHighlighter.NumberAttri.Style := cssHighlighter.NumberAttri.Style + [fsBold];
  if conf.ReadBool('highlighterNumbers','Italic',false) then
    cssHighlighter.NumberAttri.Style := cssHighlighter.NumberAttri.Style + [fsItalic];
  if conf.ReadBool('highlighterNumbers','Underline',false) then
    cssHighlighter.NumberAttri.Style := cssHighlighter.NumberAttri.Style + [fsUnderline];
  // Strings
  t  := conf.ReadString('highlighterStrings','Colour','');
  if t <> '' then cssHighlighter.StringAttri.Foreground:=HexToTColor(t);
  if conf.ReadBool('highlighterStrings','Bold',true) then
    cssHighlighter.StringAttri.Style := cssHighlighter.StringAttri.Style + [fsBold];
  if conf.ReadBool('highlighterStrings','Italic',false) then
    cssHighlighter.StringAttri.Style := cssHighlighter.StringAttri.Style + [fsItalic];
  if conf.ReadBool('highlighterStrings','Underline',false) then
    cssHighlighter.StringAttri.Style := cssHighlighter.StringAttri.Style + [fsUnderline];
  // Symbols
  t  := conf.ReadString('highlighterSymbols','Colour','');
  if t <> '' then cssHighlighter.SymbolAttri.Foreground:=HexToTColor(t);
  if conf.ReadBool('highlighterSymbols','Bold',true) then
    cssHighlighter.SymbolAttri.Style := cssHighlighter.SymbolAttri.Style + [fsBold];
  if conf.ReadBool('highlighterSymbols','Italic',false) then
    cssHighlighter.SymbolAttri.Style := cssHighlighter.SymbolAttri.Style + [fsItalic];
  if conf.ReadBool('highlighterSymbols','Underline',false) then
    cssHighlighter.SymbolAttri.Style := cssHighlighter.SymbolAttri.Style + [fsUnderline];
  conf.Free;
end;

procedure TfrmMain.saveConfig;
var
  conf: TIniFile;
begin
  conf := TIniFile.Create(appdir + 'cssmonkey.conf');
  conf.WriteString('highlighter','Font',frmOptions.editPreview.Font.Name);
  conf.WriteInteger('highlighter','FontSize',frmOptions.editPreview.Font.Size);
  conf.WriteString('highlighter','Colour',ColourToHTML(frmOptions.editPreview.Color));
  // Comments
  conf.WriteString('highlighterComments','Colour',ColourToHTML(frmOptions.highlighterCSS.CommentAttri.Foreground));
  if fsBold in frmOptions.highlighterCSS.CommentAttri.Style then
    conf.WriteBool('highlighterComments','Bold',true)
  else conf.WriteBool('highlighterComments','Bold',false);
  if fsItalic in frmOptions.highlighterCSS.CommentAttri.Style then
    conf.WriteBool('highlighterComments','Italic',true)
  else conf.WriteBool('highlighterComments','Italic',false);
  if fsUnderline in frmOptions.highlighterCSS.CommentAttri.Style then
    conf.WriteBool('highlighterComments','Underline',true)
  else conf.WriteBool('highlighterComments','Underline',false);
  // Identifiers
  conf.WriteString('highlighterIdentifiers','Colour',ColourToHTML(frmOptions.highlighterCSS.IdentifierAttri.Foreground));
  if fsBold in frmOptions.highlighterCSS.IdentifierAttri.Style then
    conf.WriteBool('highlighterIdentifiers','Bold',true)
  else conf.WriteBool('highlighterIdentifiers','Bold',false);
  if fsItalic in frmOptions.highlighterCSS.IdentifierAttri.Style then
    conf.WriteBool('highlighterIdentifiers','Italic',true)
  else conf.WriteBool('highlighterIdentifiers','Italic',false);
  if fsUnderline in frmOptions.highlighterCSS.IdentifierAttri.Style then
    conf.WriteBool('highlighterIdentifiers','Underline',true)
  else conf.WriteBool('highlighterIdentifiers','Underline',false);
  // Keys
  conf.WriteString('highlighterKeys','Colour',ColourToHTML(frmOptions.highlighterCSS.KeyAttri.Foreground));
  if fsBold in frmOptions.highlighterCSS.KeyAttri.Style then
    conf.WriteBool('highlighterKeys','Bold',true)
  else conf.WriteBool('highlighterKeys','Bold',false);
  if fsItalic in frmOptions.highlighterCSS.KeyAttri.Style then
    conf.WriteBool('highlighterKeys','Italic',true)
  else conf.WriteBool('highlighterKeys','Italic',false);
  if fsUnderline in frmOptions.highlighterCSS.KeyAttri.Style then
    conf.WriteBool('highlighterKeys','Underline',true)
  else conf.WriteBool('highlighterKeys','Underline',false);
  // Numbers
  conf.WriteString('highlighterNumbers','Colour',ColourToHTML(frmOptions.highlighterCSS.NumberAttri.Foreground));
  if fsBold in frmOptions.highlighterCSS.NumberAttri.Style then
    conf.WriteBool('highlighterNumbers','Bold',true)
  else conf.WriteBool('highlighterNumbers','Bold',false);
  if fsItalic in frmOptions.highlighterCSS.NumberAttri.Style then
    conf.WriteBool('highlighterNumbers','Italic',true)
  else conf.WriteBool('highlighterNumbers','Italic',false);
  if fsUnderline in frmOptions.highlighterCSS.NumberAttri.Style then
    conf.WriteBool('highlighterNumbers','Underline',true)
  else conf.WriteBool('highlighterNumbers','Underline',false);
  // Strings
  conf.WriteString('highlighterStrings','Colour',ColourToHTML(frmOptions.highlighterCSS.StringAttri.Foreground));
  if fsBold in frmOptions.highlighterCSS.StringAttri.Style then
    conf.WriteBool('highlighterStrings','Bold',true)
  else conf.WriteBool('highlighterStrings','Bold',false);
  if fsItalic in frmOptions.highlighterCSS.StringAttri.Style then
    conf.WriteBool('highlighterStrings','Italic',true)
  else conf.WriteBool('highlighterStrings','Italic',false);
  if fsUnderline in frmOptions.highlighterCSS.StringAttri.Style then
    conf.WriteBool('highlighterStrings','Underline',true)
  else conf.WriteBool('highlighterStrings','Underline',false);
  // Symbols
  conf.WriteString('highlighterSymbols','Colour',ColourToHTML(frmOptions.highlighterCSS.SymbolAttri.Foreground));
  if fsBold in frmOptions.highlighterCSS.SymbolAttri.Style then
    conf.WriteBool('highlighterSymbols','Bold',true)
  else conf.WriteBool('highlighterSymbols','Bold',false);
  if fsItalic in frmOptions.highlighterCSS.SymbolAttri.Style then
    conf.WriteBool('highlighterSymbols','Italic',true)
  else conf.WriteBool('highlighterSymbols','Italic',false);
  if fsUnderline in frmOptions.highlighterCSS.SymbolAttri.Style then
    conf.WriteBool('highlighterSymbols','Underline',true)
  else conf.WriteBool('highlighterSymbols','Underline',false);
  conf.Free;
end;

end.
